/* Sole is a lightweight C++11 library to generate universally unique identificators.
 * Sole provides interface for UUID versions 0, 1 and 4.

 * https://github.com/r-lyeh/sole
 * Copyright (c) 2013,2014,2015 r-lyeh. zlib/libpng licensed.

 * Based on code by Dmitri Bouianov, Philip O'Toole, Poco C++ libraries and anonymous
 * code found on the net. Thanks guys!

 * Theory: (see Hoylen's answer at [1])
 * - UUID version 1 (48-bit MAC address + 60-bit clock with a resolution of 100ns)
 *   Clock wraps in 3603 A.D.
 *   Up to 10000000 UUIDs per second.
 *   MAC address revealed.
 *
 * - UUID Version 4 (122-bits of randomness)
 *   See [2] or other analysis that describe how very unlikely a duplicate is.
 *
 * - Use v1 if you need to sort or classify UUIDs per machine.
 *   Use v1 if you are worried about leaving it up to probabilities (e.g. your are the
 *   type of person worried about the earth getting destroyed by a large asteroid in your
 *   lifetime). Just use a v1 and it is guaranteed to be unique till 3603 AD.
 *
 * - Use v4 if you are worried about security issues and determinism. That is because
 *   v1 UUIDs reveal the MAC address of the machine it was generated on and they can be
 *   predictable. Use v4 if you need more than 10 million uuids per second, or if your
 *   application wants to live past 3603 A.D.

 * Additionally a custom UUID v0 is provided:
 * - 16-bit PID + 48-bit MAC address + 60-bit clock with a resolution of 100ns since Unix epoch
 * - Format is EPOCH_LOW-EPOCH_MID-VERSION(0)|EPOCH_HI-PID-MAC
 * - Clock wraps in 3991 A.D.
 * - Up to 10000000 UUIDs per second.
 * - MAC address and PID revealed.

 * References:
 * - [1] http://stackoverflow.com/questions/1155008/how-unique-is-uuid
 * - [2] http://en.wikipedia.org/wiki/UUID#Random%5FUUID%5Fprobability%5Fof%5Fduplicates
 * - http://en.wikipedia.org/wiki/Universally_unique_identifier
 * - http://en.cppreference.com/w/cpp/numeric/random/random_device
 * - http://www.itu.int/ITU-T/asn1/uuid.html f81d4fae-7dec-11d0-a765-00a0c91e6bf6

 * - rlyeh ~~ listening to Hedon Cries / Until The Sun Goes up
 */

 //////////////////////////////////////////////////////////////////////////////////////

#pragma once
#include <stdint.h>
#include <stdio.h>     // for size_t; should be stddef.h instead; however, clang+archlinux fails when compiling it (@Travis-Ci)
#include <sys/types.h> // for uint32_t; should be stdint.h instead; however, GCC 5 on OSX fails when compiling it (See issue #11)
#include <functional>
#include <string>

// public API

#define SOLE_VERSION "1.0.4" /* (2022/04/09): Fix potential threaded issues (fix #18, PR #39) and a socket leak (fix #38)
#define SOLE_VERSION "1.0.3" // (2022/01/17): Merge fixes by @jasonwinterpixel(emscripten) + @jj-tetraquark(get_any_mac)
#define SOLE_VERSION "1.0.2" // (2021/03/16): Merge speed improvements by @vihangm
#define SOLE_VERSION "1.0.1" // (2017/05/16): Improve UUID4 and base62 performance; fix warnings
#define SOLE_VERSION "1.0.0" // (2016/02/03): Initial semver adherence; Switch to header-only; Remove warnings */

namespace sole
{
    // 128-bit basic UUID type that allows comparison and sorting.
    // Use .str() for printing and .pretty() for pretty printing.
    // Also, ostream friendly.
    struct uuid
    {
        uint64_t ab;
        uint64_t cd;

        bool operator==(const uuid& other) const;
        bool operator!=(const uuid& other) const;
        bool operator <(const uuid& other) const;

        std::string pretty() const;
        std::string base62() const;
        std::string str() const;

        template<typename ostream>
        inline friend ostream& operator<<(ostream& os, const uuid& self) {
            return os << self.str(), os;
        }
    };

    // Generators
    uuid uuid0(); // UUID v0, pro: unique; cons: MAC revealed, pid revealed, predictable.
    uuid uuid1(); // UUID v1, pro: unique; cons: MAC revealed, predictable.
    uuid uuid4(); // UUID v4, pros: anonymous, fast; con: uuids "can clash"

    // Rebuilders
    uuid rebuild(uint64_t ab, uint64_t cd);
    uuid rebuild(const std::string& uustr);
}

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable:4127)
#endif

namespace std {
    template<>
    struct hash< sole::uuid > {
    public:
        // hash functor: hash uuid to size_t value by pseudorandomizing transform
        size_t operator()(const sole::uuid& uuid) const {
            if (sizeof(size_t) > 4) {
                return size_t(uuid.ab ^ uuid.cd);
            }
            else {
                uint64_t hash64 = uuid.ab ^ uuid.cd;
                return size_t(uint32_t(hash64 >> 32) ^ uint32_t(hash64));
            }
        }
    };
}

#ifdef _MSC_VER
#pragma warning(pop)
#endif

// implementation

#include <memory.h>
#include <stdint.h>
#include <stdio.h>
#include <time.h>

#include <cstring>
#include <ctime>

#include <iomanip>
#include <random>
#include <sstream>
#include <string>
#include <vector>

#if defined(_WIN32)
#   include <winsock2.h>
#   include <process.h>
#   include <iphlpapi.h>
#   ifdef _MSC_VER
#   pragma comment(lib,"iphlpapi.lib")
#   endif
#   define $windows $yes
#elif defined(__FreeBSD__) || defined(__NetBSD__) || \
        defined(__OpenBSD__) || defined(__MINT__) || defined(__bsdi__)
#   include <ifaddrs.h>
#   include <net/if_dl.h>
#   include <sys/socket.h>
#   include <sys/time.h>
#   include <sys/types.h>
#   include <unistd.h>
#   define $bsd $yes
#elif (defined(__APPLE__) && defined(__MACH__))
#   include <ifaddrs.h>
#   include <net/if_dl.h>
#   include <sys/socket.h>
#   include <sys/time.h>
#   include <sys/types.h>
#   include <unistd.h>
#   pragma clang diagnostic push
#   pragma clang diagnostic ignored "-Wdollar-in-identifier-extension"
#   define $osx $yes
#elif defined(__linux__)
#   include <arpa/inet.h>
#   include <ifaddrs.h>
#   include <net/if.h>
#   include <netinet/in.h>
#   include <sys/ioctl.h>
#   include <sys/socket.h>
#   include <sys/time.h>
#   include <unistd.h>
#   define $linux $yes
#else //elif defined(__unix__)
#	if defined(__EMSCRIPTEN__)
#		define $emscripten $yes
#	endif
#   if defined(__VMS)
#      include <ioctl.h>
#      include <inet.h>
#   else
#      include <sys/ioctl.h>
#      include <arpa/inet.h>
#   endif
#   if defined(sun) || defined(__sun)
#      include <sys/sockio.h>
#   endif
#   include <net/if.h>
#   include <net/if_arp.h>
#   include <netdb.h>
#   include <netinet/in.h>
#   include <sys/socket.h>
#   include <sys/time.h>
#   include <sys/types.h>
#   include <unistd.h>
#   define $unix $yes
#endif

#ifdef _MSC_VER
#   define $msvc  $yes
#endif

#if defined(__GNUC__) && (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 < 50100)
namespace std
{
    static inline std::string put_time(const std::tm* tmb, const char* fmt) {
        std::string s(128, '\0');
        while (!strftime(&s[0], s.size(), fmt, tmb))
            s.resize(s.size() + 128);
        return s;
    }
}
#endif

////////////////////////////////////////////////////////////////////////////////////

#ifdef  $windows
#define $welse   $no
#else
#define $windows $no
#define $welse   $yes
#endif

#ifdef  $bsd
#define $belse   $no
#else
#define $bsd     $no
#define $belse   $yes
#endif

#ifdef  $linux
#define $lelse   $no
#else
#define $linux   $no
#define $lelse   $yes
#endif

#ifdef  $unix
#define $uelse   $no
#else
#define $unix    $no
#define $uelse   $yes
#endif

#ifdef  $osx
#define $oelse   $no
#else
#define $osx     $no
#define $oelse   $yes
#endif

#ifdef  $msvc
#define $melse   $no
#else
#define $msvc    $no
#define $melse   $yes
#endif

#ifdef $emscripten
#define $emselse	$no
#else
#define $emselse	$yes
#endif

#define $yes(...) __VA_ARGS__
#define $no(...)

inline bool sole::uuid::operator==(const sole::uuid& other) const {
    return ab == other.ab && cd == other.cd;
}
inline bool sole::uuid::operator!=(const sole::uuid& other) const {
    return !operator==(other);
}
inline bool sole::uuid::operator<(const sole::uuid& other) const {
    if (ab < other.ab) return true;
    if (ab > other.ab) return false;
    if (cd < other.cd) return true;
    return false;
}

namespace sole {

    inline std::string printftime(uint64_t timestamp_secs = 0, const std::string& locale = std::string()) {
        std::string timef;
        try {
            // Taken from parameter
            //std::string locale; // = "es-ES", "Chinese_China.936", "en_US.UTF8", etc...
            std::time_t t = timestamp_secs;
            std::tm tm;
            $msvc(
                localtime_s(&tm, &t);
            )
                $melse(
                    $windows(tm = *localtime(&t); )
                    $welse(localtime_r(&t, &tm); )
                )

                std::stringstream ss;
            $melse(
                std::locale lc(locale.c_str());
            ss.imbue(lc);
            )
                ss << std::put_time(&tm, "\"%c\"");

            timef = ss.str();
        }
        catch (...) {
            timef = "\"\"";
        }
        return timef;
    }

    inline std::string uuid::pretty() const {
        std::stringstream ss;

        uint64_t a = (ab >> 32);
        uint64_t b = (ab & 0xFFFFFFFF);
        uint64_t c = (cd >> 32);
        uint64_t d = (cd & 0xFFFFFFFF);

        int version = (b & 0xF000) >> 12;
        uint64_t timestamp = ((b & 0x0FFF) << 48) | ((b >> 16) << 32) | a; // in 100ns units

        ss << "version=" << (version) << ',';

        if (version == 1)
            timestamp = timestamp - 0x01b21dd213814000ULL; // decrement Gregorian calendar

        ss << std::hex << std::nouppercase << std::setfill('0');
        version <= 1 && ss << "timestamp=" << printftime(timestamp / 10000000) << ',';
        version <= 1 && ss << "mac=" << std::setw(4) << (c & 0xFFFF) << std::setw(8) << d << ',';
        version == 4 && ss << "randbits=" << std::setw(8) << (ab & 0xFFFFFFFFFFFF0FFFULL) << std::setw(8) << (cd & 0x3FFFFFFFFFFFFFFFULL) << ',';

        ss << std::dec;
        version == 0 && ss << "pid=" << std::setw(4) << (c >> 16) << ',';
        version == 1 && ss << "clock_seq=" << std::setw(4) << ((c >> 16) & 0x3FFF) << ',';

        return ss.str();
    }

    inline std::string uuid::str() const {
        char uustr[] = "00000000-0000-0000-0000-000000000000";
        constexpr char encode[] = "0123456789abcdef";

        size_t bit = 15;
        for (size_t i = 0; i < 18; i++) {
            if (i == 8 || i == 13) {
                continue;
            }
            uustr[i] = encode[ab >> 4 * bit & 0x0f];
            bit--;
        }

        bit = 15;
        for (size_t i = 18; i < 36; i++) {
            if (i == 18 || i == 23) {
                continue;
            }
            uustr[i] = encode[cd >> 4 * bit & 0x0f];
            bit--;
        }

        return std::string(uustr);
    }

    inline std::string uuid::base62() const {
        int base62len = 10 + 26 + 26;
        const char base62[] =
            "0123456789"
            "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
            "abcdefghijklmnopqrstuvwxyz";
        char res[24], * end = &res[24]; *(--end) = '\0';
        uint64_t rem, AB = ab, CD = cd;
        do {
            rem = CD % base62len;
            *--end = base62[int(rem)];
            CD /= base62len;
        } while (CD > 0);
        *--end = '-';
        do {
            rem = AB % base62len;
            *--end = base62[int(rem)];
            AB /= base62len;
        } while (AB > 0);
        return end;
    }

    //////////////////////////////////////////////////////////////////////////////////////
    // multiplatform clock_gettime()

    $windows(
        struct timespec {
        uint64_t tv_sec;
        uint64_t tv_nsec;
    };
    struct timezone {
        int  tz_minuteswest; /* minutes W of Greenwich */
        int  tz_dsttime;     /* type of dst correction */
    };
    inline int gettimeofday(struct timeval* tv, struct timezone* tz) {
        FILETIME ft;
        uint64_t tmpres = 0;

        if (NULL != tv) {
            GetSystemTimeAsFileTime(&ft);

            // The GetSystemTimeAsFileTime returns the number of 100 nanosecond
            // intervals since Jan 1, 1601 in a structure. Copy the high bits to
            // the 64 bit tmpres, shift it left by 32 then or in the low 32 bits.
            tmpres |= ft.dwHighDateTime;
            tmpres <<= 32;
            tmpres |= ft.dwLowDateTime;

            // Convert to microseconds by dividing by 10
            tmpres /= 10;

            // The Unix epoch starts on Jan 1 1970.  Need to subtract the difference
            // in seconds from Jan 1 1601.
            tmpres -= 11644473600000000ULL;

            // Finally change microseconds to seconds and place in the seconds value.
            // The modulus picks up the microseconds.
            tv->tv_sec = static_cast<long>(tmpres / 1000000UL);
            tv->tv_usec = (tmpres % 1000000UL);
        }

        if (NULL != tz) {
            static bool once = true;
            if (once) {
                once = false;
                _tzset();
            }

            long timezoneSecs = 0;
            int daylight = 0;

            $msvc(
                _get_timezone(&timezoneSecs);
            _get_daylight(&daylight);
            )
                $melse(
                    timezoneSecs = _timezone;
            daylight = _daylight;
            )

                tz->tz_minuteswest = timezoneSecs / 60;
            tz->tz_dsttime = daylight;
        }

        return 0;
    }
    )
        $lelse($belse($emselse( // if not linux, if not bsd, if not emscripten... valid for apple/win32
            inline int clock_gettime(int /*clk_id*/, struct timespec* t) {
        struct timeval now;
        int rv = gettimeofday(&now, NULL);
        if (rv) return rv;
        t->tv_sec = now.tv_sec;
        t->tv_nsec = now.tv_usec * 1000;
        return 0;
    }
    )))

        //////////////////////////////////////////////////////////////////////////////////////
        // Timestamp and MAC interfaces

        // Returns number of 100ns intervals
        inline uint64_t get_time(uint64_t offset) {
        struct timespec tp;
        clock_gettime(0 /*CLOCK_REALTIME*/, &tp);

        // Convert to 100-nanosecond intervals
        uint64_t uuid_time;
        uuid_time = tp.tv_sec * 10000000;
        uuid_time = uuid_time + (tp.tv_nsec / 100);

        // If the clock looks like it went backwards, or is the same, increment it.
        static uint64_t last_uuid_time = 0;
        if (last_uuid_time >= uuid_time)
            uuid_time = ++last_uuid_time;
        else
            last_uuid_time = uuid_time;

        uuid_time = uuid_time + offset;

        return uuid_time;
    }

    // Looks for first MAC address of any network device, any size.
    inline bool get_any_mac(std::vector<unsigned char>& _node) {
        $windows({
            PIP_ADAPTER_INFO pAdapterInfo;
            PIP_ADAPTER_INFO pAdapter = 0;
            ULONG len = sizeof(IP_ADAPTER_INFO);
            pAdapterInfo = reinterpret_cast<IP_ADAPTER_INFO*>(new char[len]);

            // Make an initial call to GetAdaptersInfo to get
            // the necessary size into len
            DWORD rc = GetAdaptersInfo(pAdapterInfo, &len);
            if (rc == ERROR_BUFFER_OVERFLOW)
            {
                delete[] reinterpret_cast<char*>(pAdapterInfo);
                pAdapterInfo = reinterpret_cast<IP_ADAPTER_INFO*>(new char[len]);
            }
            else if (rc != ERROR_SUCCESS)
            {
                return $no("cannot get network adapter list") false;
            }

            bool found = false, gotten = false;
            if (GetAdaptersInfo(pAdapterInfo, &len) == NO_ERROR)
            {
                gotten = true;

                pAdapter = pAdapterInfo;
                while (pAdapter && !found)
                {
                    if (pAdapter->Type == MIB_IF_TYPE_ETHERNET && pAdapter->AddressLength > 0)
                    {
                        _node.resize(pAdapter->AddressLength);
                        std::memcpy(_node.data(), pAdapter->Address, _node.size());
                        found = true;
                    }
                    pAdapter = pAdapter->Next;
                }
            }

            delete[] reinterpret_cast<char*>(pAdapterInfo);

            if (!gotten)
                return $no("cannot get network adapter list") false;

            if (!found)
                return $no("no Ethernet adapter found") false;

            return true;
            })

            $bsd({
                struct ifaddrs* ifaphead;
                int rc = getifaddrs(&ifaphead);
                if (rc) return $no("cannot get network adapter list") false;

                bool foundAdapter = false;
                for (struct ifaddrs* ifap = ifaphead; ifap; ifap = ifap->ifa_next)
                {
                    if (ifap->ifa_addr && ifap->ifa_addr->sa_family == AF_LINK)
                    {
                        struct sockaddr_dl* sdl = reinterpret_cast<struct sockaddr_dl*>(ifap->ifa_addr);
                        caddr_t ap = (caddr_t)(sdl->sdl_data + sdl->sdl_nlen);
                        int alen = sdl->sdl_alen;
                        if (ap && alen > 0)
                        {
                            _node.resize(alen);
                            std::memcpy(_node.data(), ap, _node.size());
                            foundAdapter = true;
                            break;
                        }
                    }
                }
                freeifaddrs(ifaphead);
                if (!foundAdapter) return $no("cannot determine MAC address (no suitable network adapter found)") false;
                return true;
                })

                $osx({
                    struct ifaddrs* ifaphead;
                    int rc = getifaddrs(&ifaphead);
                    if (rc) return $no("cannot get network adapter list") false;

                    bool foundAdapter = false;
                    for (struct ifaddrs* ifap = ifaphead; ifap; ifap = ifap->ifa_next)
                    {
                        if (ifap->ifa_addr && ifap->ifa_addr->sa_family == AF_LINK)
                        {
                            struct sockaddr_dl* sdl = reinterpret_cast<struct sockaddr_dl*>(ifap->ifa_addr);
                            caddr_t ap = (caddr_t)(sdl->sdl_data + sdl->sdl_nlen);
                            int alen = sdl->sdl_alen;
                            if (ap && alen > 0)
                            {
                                _node.resize(alen);
                                std::memcpy(_node.data(), ap, _node.size());
                                foundAdapter = true;
                                break;
                            }
                        }
                    }
                    freeifaddrs(ifaphead);
                    if (!foundAdapter) return $no("cannot determine MAC address (no suitable network adapter found)") false;
                    return true;
                    })

                    $linux({
                        struct ifaddrs* ifaphead;
                        if (getifaddrs(&ifaphead) == -1) return $no("cannot get network adapter list") false;

                        bool foundAdapter = false;
                        for (struct ifaddrs* ifap = ifaphead; ifap; ifap = ifap->ifa_next)
                        {
                            struct ifreq ifr;
                            int s = socket(PF_INET, SOCK_DGRAM, 0);
                            if (s == -1) continue;

                            if (std::strcmp("lo", ifap->ifa_name) == 0) { close(s); continue; }  // loopback address is zero

                            std::strcpy(ifr.ifr_name, ifap->ifa_name);
                            int rc = ioctl(s, SIOCGIFHWADDR, &ifr);
                            close(s);
                            if (rc < 0) continue;
                            struct sockaddr* sa = reinterpret_cast<struct sockaddr*>(&ifr.ifr_addr);
                            _node.resize(sizeof(sa->sa_data));
                            std::memcpy(_node.data(), sa->sa_data, _node.size());
                            foundAdapter = true;
                            break;
                        }
                        freeifaddrs(ifaphead);
                        if (!foundAdapter) return $no("cannot determine MAC address (no suitable network adapter found)") false;
                        return true;

                        })

                        $unix({
                            char name[HOST_NAME_MAX];
                            if (gethostname(name, sizeof(name)))
                                return $no("cannot get host name") false;

                            struct hostent* pHost = gethostbyname(name);
                            if (!pHost) return $no("cannot get host IP address") false;

                            int s = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
                            if (s == -1) return $no("cannot open socket") false;

                            struct arpreq ar;
                            std::memset(&ar, 0, sizeof(ar));
                            struct sockaddr_in* pAddr = reinterpret_cast<struct sockaddr_in*>(&ar.arp_pa);
                            pAddr->sin_family = AF_INET;
                            std::memcpy(&pAddr->sin_addr, *pHost->h_addr_list, sizeof(struct in_addr));
                            int rc = ioctl(s, SIOCGARP, &ar);
                            close(s);
                            if (rc < 0) return $no("cannot get MAC address") false;
                            _node.resize(sizeof(ar.arp_ha.sa_data));
                            std::memcpy(_node.data(), ar.arp_ha.sa_data, _node.size());
                            return true;
                            })
    }

    // Looks for first MAC address of any network device, size truncated to 48bits.
    inline uint64_t get_any_mac48() {
        std::vector<unsigned char> node;
        if (get_any_mac(node)) {
            std::stringstream ss;
            ss << std::hex << std::setfill('0');
            node.resize(6);
            for (unsigned i = 0; i < 6; ++i)
                ss << std::setw(2) << int(node[i]);
            uint64_t t;
            if (ss >> t)
                return t;
        }
        return 0;
    }

    //////////////////////////////////////////////////////////////////////////////////////
    // UUID implementations

    inline uuid uuid4() {
        static $msvc(__declspec(thread)) $melse(__thread) std::random_device rd;
        static $msvc(__declspec(thread)) $melse(__thread) std::uniform_int_distribution<uint64_t> dist(0, (uint64_t)(~0));

        uuid my;

        my.ab = dist(rd);
        my.cd = dist(rd);

        my.ab = (my.ab & 0xFFFFFFFFFFFF0FFFULL) | 0x0000000000004000ULL;
        my.cd = (my.cd & 0x3FFFFFFFFFFFFFFFULL) | 0x8000000000000000ULL;

        return my;
    }

    inline uuid uuid1() {
        // Number of 100-ns intervals since 00:00:00.00 15 October 1582; [ref] uuid.py
        uint64_t ns100_intervals = get_time(0x01b21dd213814000ULL);
        uint16_t clock_seq = (uint16_t)(ns100_intervals & 0x3fff);  // 14-bits max
        uint64_t mac = get_any_mac48();                               // 48-bits max

        uint32_t time_low = ns100_intervals & 0xffffffff;
        uint16_t time_mid = (ns100_intervals >> 32) & 0xffff;
        uint16_t time_hi_version = (ns100_intervals >> 48) & 0xfff;
        uint8_t clock_seq_low = clock_seq & 0xff;
        uint8_t clock_seq_hi_variant = (clock_seq >> 8) & 0x3f;

        uuid u;
        uint64_t& upper_ = u.ab;
        uint64_t& lower_ = u.cd;

        // Build the high 32 bytes
        upper_ = (uint64_t)time_low << 32;
        upper_ |= (uint64_t)time_mid << 16;
        upper_ |= (uint64_t)time_hi_version;

        // Build the low 32 bytes, using the clock sequence number
        lower_ = (uint64_t)((clock_seq_hi_variant << 8) | clock_seq_low) << 48;
        lower_ |= mac;

        // Set the variant to RFC 4122.
        lower_ &= ~((uint64_t)0xc000 << 48);
        lower_ |= (uint64_t)0x8000 << 48;

        // Set the version number.
        enum { version = 1 };
        upper_ &= ~0xf000;
        upper_ |= version << 12;

        return u;
    }

    inline uuid uuid0() {
        // Number of 100-ns intervals since Unix epoch time
        uint64_t ns100_intervals = get_time(0);
        uint64_t pid = $windows(_getpid()) $welse(getpid());
        uint16_t pid16 = (uint16_t)(pid & 0xffff); // 16-bits max
        uint64_t mac = get_any_mac48();              // 48-bits max

        uint32_t time_low = ns100_intervals & 0xffffffff;
        uint16_t time_mid = (ns100_intervals >> 32) & 0xffff;
        uint16_t time_hi_version = (ns100_intervals >> 48) & 0xfff;
        uint8_t pid_low = pid16 & 0xff;
        uint8_t pid_hi = (pid16 >> 8) & 0xff;

        uuid u;
        uint64_t& upper_ = u.ab;
        uint64_t& lower_ = u.cd;

        // Build the high 32 bytes.
        upper_ = (uint64_t)time_low << 32;
        upper_ |= (uint64_t)time_mid << 16;
        upper_ |= (uint64_t)time_hi_version;

        // Build the low 32 bytes, using the mac and pid number.
        lower_ = (uint64_t)((pid_hi << 8) | pid_low) << 48;
        lower_ |= mac;

        // Set the version number.
        enum { version = 0 };
        upper_ &= ~0xf000;
        upper_ |= version << 12;

        return u;
    }

    inline uuid rebuild(uint64_t ab, uint64_t cd) {
        uuid u;
        u.ab = ab; u.cd = cd;
        return u;
    }

    inline uuid rebuild(const std::string& uustr) {
        uuid u = { 0, 0 };
        auto idx = uustr.find_first_of("-");
        if (idx != std::string::npos) {
            // single separator, base62 notation
            if (uustr.find_first_of("-", idx + 1) == std::string::npos) {
                auto rebase62 = [&](const char* input, size_t limit) -> uint64_t {
                    int base62len = 10 + 26 + 26;
                    auto strpos = [](char ch) -> size_t {
                        if (ch >= 'a') return ch - 'a' + 10 + 26;
                        if (ch >= 'A') return ch - 'A' + 10;
                        return ch - '0';
                    };
                    uint64_t res = strpos(input[0]);
                    for (size_t i = 1; i < limit; ++i)
                        res = base62len * res + strpos(input[i]);
                    return res;
                };
                u.ab = rebase62(&uustr[0], idx);
                u.cd = rebase62(&uustr[idx + 1], uustr.size() - (idx + 1));
            }
            // else classic hex notation
            else {
                if (uustr[8] == '-' || uustr[13] == '-' || uustr[18] == '-' || uustr[23] == '-') {
                    auto decode = [](char ch) -> size_t {
                        if ('f' >= ch && ch >= 'a') return ch - 'a' + 10;
                        if ('F' >= ch && ch >= 'A') return ch - 'A' + 10;
                        if ('9' >= ch && ch >= '0') return ch - '0';
                        return 0;
                    };
                    for (size_t i = 0; i < 18; i++) {
                        if (i == 8 || i == 13) {
                            continue;
                        }
                        u.ab = u.ab << 4 | decode(uustr[i]);
                    }
                    for (size_t i = 19; i < 36; i++) {
                        if (i == 23) {
                            continue;
                        }
                        u.cd = u.cd << 4 | decode(uustr[i]);
                    }
                }
            }
        }
        return u;
    }

} // ::sole

#undef $bsd
#undef $belse
#undef $linux
#undef $lelse
#undef $osx
#undef $oelse
#undef $unix
#undef $uelse
#undef $windows
#undef $welse
#undef $yes
#undef $no

// Pop disabled warnings
#if (defined(__APPLE__) && defined(__MACH__))
#pragma clang diagnostic pop
#endif

#ifdef SOLE_BUILD_DEMO
// g++ users: `g++ demo.cc -std=c++11 -lrt -o sample`
// visual studio: `cl.exe demo.cc sole.cpp`

#include <iostream>

int main() {
    sole::uuid u0 = sole::uuid0(), u1 = sole::uuid1(), u4 = sole::uuid4();

    std::cout << "uuid v0 string : " << u0 << std::endl;
    std::cout << "uuid v0 base62 : " << u0.base62() << std::endl;
    std::cout << "uuid v0 pretty : " << u0.pretty() << std::endl << std::endl;

    std::cout << "uuid v1 string : " << u1 << std::endl;
    std::cout << "uuid v1 base62 : " << u1.base62() << std::endl;
    std::cout << "uuid v1 pretty : " << u1.pretty() << std::endl << std::endl;

    std::cout << "uuid v4 string : " << u4 << std::endl;
    std::cout << "uuid v4 base62 : " << u4.base62() << std::endl;
    std::cout << "uuid v4 pretty : " << u4.pretty() << std::endl << std::endl;

    u1 = sole::rebuild("F81D4FAE-7DEC-11D0-A765-00A0C91E6BF6");
    u4 = sole::rebuild("GITheR4tLlg-BagIW20DGja");

    std::cout << "uuid v1 rebuilt : " << u1 << " -> " << u1.pretty() << std::endl;
    std::cout << "uuid v4 rebuilt : " << u4 << " -> " << u4.pretty() << std::endl;
}

#endif

#ifdef SOLE_BUILD_TESTS
// g++ users: `g++ sole.cxx -std=c++11 -lrt -o tests`
// visual studio: `cl.exe sole.cxx`

#include <cassert>
#include <set>
#include <ratio>
#include <chrono>
#include <iostream>

using namespace sole;

namespace run
{
    auto epoch = []() {
        return std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
    };

    template<typename FN>
    void benchmark(const FN& fn, const std::string& name) {
        std::cout << "Benchmarking " << name << "... " << std::flush;

        auto then = epoch();

        while (epoch() == then);
        then = epoch();

        unsigned c = 0;
        while (epoch() == then) c = (fn(), ++c);

        std::cout << (c) << " uuids/sec" << std::endl;
    }

    template<typename FN>
    void tests(const FN& fn) {
        unsigned numtests = ~0;
        std::cout << "Testing for " << numtests << " collisions... " << std::endl;

        auto then = epoch();

        std::set<uuid> all;
        for (unsigned i = 0; i < numtests; ++i) {
            auto now = epoch();
            if (now != then) {
                then = now;
                double pct6digs = (int((double(i) / (unsigned)(~0)) * 1e4) / double(1e4));
                std::cout << '\r' << i << " uuids generated, no collision (" << pct6digs << "%)" << std::flush;
            }
            sole::uuid my_uuid = fn();
            assert(all.find(my_uuid) == all.end() && "error: UUIDs just collided! is std::random_device a real random generator?");
            all.insert(my_uuid);
        }
    }

    template<typename FN>
    void verify(const FN& fn) {
        std::cout << "Verifying serialization of 1 million UUIDs... " << std::flush;

        for (unsigned i = 0; i < 1000000; ++i) {
            sole::uuid uuid = fn();
            sole::uuid rebuilt1 = sole::rebuild(uuid.str());
            sole::uuid rebuilt2 = sole::rebuild(uuid.base62());
            assert(rebuilt1 == uuid && "error: rebuild() or .str() failed");
            assert(rebuilt2 == uuid && "error: rebuild() or .base62() failed");
        }

        std::cout << "ok" << std::endl;
    }
}

int main() {
    assert(sizeof(sole::uuid) * 8 == 128);
    assert(sizeof(sole::uuid0().ab) * 8 == 64);
    assert(sizeof(sole::uuid0().cd) * 8 == 64);

    run::benchmark(uuid0, "v0");
    run::benchmark(uuid1, "v1");
    run::benchmark(uuid4, "v4");

    auto uustr = uuid4().str();
    run::benchmark([=]() { sole::rebuild(uustr); }, "rebuild");

    auto uuid = uuid4();
    run::benchmark([=]() { uuid.str(); }, "str");

    run::verify(uuid4);             // use fastest implementation

//  run::tests(uuid0);              // not applicable
//  run::tests(uuid1);              // not applicable
    run::tests(uuid4);
}

#endif
